/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package fr.sorbonne.inf5.yams.model;

import fr.sorbonne.inf5.yams.exceptions.AccessDataException;
import fr.sorbonne.inf5.yams.exceptions.CombinaisonNotFoundException;
import fr.sorbonne.inf5.yams.exceptions.DeNotRunException;
import fr.sorbonne.inf5.yams.exceptions.JoueurNotFoundException;
import fr.sorbonne.inf5.yams.exceptions.PlayAlreadyBeginException;
import fr.sorbonne.inf5.yams.exceptions.TourPlayerOverException;
import fr.sorbonne.inf5.yams.model.combinaison.Combinaison;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Cette classe modélise le jeu du Yam's et toutes les actions que l'on peut faire
 * en y jouant
 * @author Oukherfellah Riad
 * @author Dubos Romain
 */
public class Yams {

    private Map<Combinaison, String> combinaisons;
    private Map<De, Boolean> des;
    private Map<AbstractJoueur, Score> joueurs;
    private AbstractJoueur[] ordreJoueurs;
    private Palmares<LignePalmares> palmares;
    private int indJoueurCourant;
    private int nbLance;
    private int nbLanceCourant;
    private boolean joue;

    /**
     * Constructeur qui crée un jeu du yams avec 5 dés de face numérique de 1 à 6
     * avec les combinaisons suivantes : Full
     * 
     */
    public Yams(List<De> listeDe, Map<Combinaison, String> combinaisons) {
        this.combinaisons = combinaisons;
        this.des = new HashMap<De, Boolean>();
        this.joueurs = new HashMap<AbstractJoueur, Score>();
        for (int i = 0; i < listeDe.size(); i++) {
            this.des.put(listeDe.get(i), false);
        }
        this.palmares = new PalmaresDB();
        this.joue = false;
        this.nbLanceCourant = 0;
        this.nbLance = 3;

    }

    /**
     * Lance les dés non bloqués
     * @return La liste de dés après avoir lancé ceux qui n'était pas bloqués
     */
    public List<De> lancerDes() throws TourPlayerOverException {
        List<De> listeDe = new ArrayList<De>();
        if (this.nbLanceCourant < this.nbLance) {
            this.nbLanceCourant++;
            for (De d : des.keySet()) {
                if (!des.get(d)) {
                    listeDe.add(d.lancerDe());
                } else {
                    listeDe.add(d);
                }
            }
        } else {
            throw new TourPlayerOverException();
        }
        return listeDe;
    }

    /**
     * Renvoie le nombre de lancés par Joueur, La valeur par défaut est 3
     * @return le nombre de lancés autorisé par joueur
     */
    public int getNbLance() {
        return nbLance;
    }

    /**
     * Renvoie vrai si le joueur peut encore lancer les dés faux sinon
     * @return vrai si le joueur courant peut encore lancer les dés faux sinon
     */
    public boolean peutEncoreLanceDe() {
        return this.nbLanceCourant < this.nbLance;
    }

    /**
     * Renvoie le nombre de lancer restant à effectué
     * @return nb lancer restant
     */
    public int getNbLancerRestant() {
        return (this.nbLance - this.nbLanceCourant);
    }

    /**
     * Pour Changer le nombre de lancé de dés par joueur
     * @param nbLance le nouveau nombre de lancé
     * @throws PlayAlreadyBeginException si la méthode est appelé au cours d'un jeu
     */
    public void setNbLance(int nbLance) throws PlayAlreadyBeginException {
        if (this.joue) {
            this.nbLance = nbLance;
        } else {
            throw new PlayAlreadyBeginException();
        }
    }

    /**
     * Renvoie la liste des dés du jeu (Se sont les dés qui ont été lancés)
     * @return la liste des dés du jeu
     */
    public List<De> renvoyerDe() {
        List<De> listeDe = new ArrayList<De>();
        for (De d : des.keySet()) {
            listeDe.add(d);
        }
        return listeDe;
    }

    /**
     * Permet de bloquer les dés contenu dans la liste c'est à dire ne pas les lancer
     * au prochain lancement de dés
     * @param listeDe une liste contenant les dés à bloquer
     */
    public void bloquerDe(List<De> listeDe) {
        for (int i = 0; i < listeDe.size(); i++) {
            if (des.containsKey(listeDe.get(i))) {
                des.put(listeDe.get(i), true);
            }
        }
    }

    /**
     * Permet de bloquer le dé en paramètre c'est à dire ne pas le lancer
     * au prochain lancement de dés
     * @param de le dé à bloquer
     */
    public void bloquerDe(De de) {
        des.put(de, true);

    }

    /**
     * Permet de debloquer le dé en paramètre c'est à dire de le lancer
     * au prochain lancement de dés
     * @param de le dé à bloquer
     */
    public void deBloquerDe(De de) {
        des.put(de, false);
    }

    /**
     * Permet de savoir si un dé est bloqué
     * @param de dé à controlé
     * @return true si dé bloqué
     */
    public boolean isBloque(De de) {
        return this.des.get(de);
    }

    /**
     * Initialise les variables pour le jeu
     * indice du joueur courant
     * la variable pour indiquer que le jeu est lancé
     */
    private void initialiserJeu() {
        this.joue = true;
        this.indJoueurCourant = 0;
        this.nbLanceCourant = 0;
    }

    /**
     * Change le joueur courant
     */
    public void changerJoueur() {
        this.nbLanceCourant = 0;
        this.indJoueurCourant++;
        if (this.indJoueurCourant >= this.ordreJoueurs.length) {
            this.indJoueurCourant = 0;
        }
        for (De de : des.keySet()) {
            this.des.put(de, false);
            de.reinitialiterDe();
        }
    }

    /**
     * Retourne le joueur courant
     * @return le joueur courant
     */
    public AbstractJoueur retournerJoueurCourant() {
        return this.ordreJoueurs[this.indJoueurCourant];
    }

    /**
     * Donne un apercu des résultats des combinaison pour la liste de dés courante
     * @return map avec la combinaison associée à son résultat en entier
     * @throws DeNotRunException si les dés non pas été lancés
     */
    public Map<Combinaison, Integer> calculCombinaisonInfo() throws DeNotRunException {
        Map<Combinaison, Integer> returnMap = new HashMap<Combinaison, Integer>();
        for (Combinaison combinaison : combinaisons.keySet()) {
            returnMap.put(combinaison, combinaison.calculer(this.renvoyerDe()));
        }
        return returnMap;
    }

    /**
     * Valide la combinaison en parametre suivant la valeur des dés courant
     * @param combinaison La combinaison que l'on souhaite valider
     * @throws DeNotRunException si les dés n'ont pas été la
     */
    public void validerCombinaison(Combinaison combinaison) throws DeNotRunException {
        Score score = joueurs.get(ordreJoueurs[indJoueurCourant]);
        score.ajouterScore(combinaison, combinaison.calculer(this.renvoyerDe()));

    }

    /**
     * Renvoie la combinaison dont le nom est passé en paramètre
     * @param combinaison le nom de la combinaison
     * @return la combinaison associé
     * @throws CombinaisonNotFoundException si la combinaison ne fait pas partie du jeu
     */
    public Combinaison renvoyerCombinaison(String combinaison) throws CombinaisonNotFoundException {
        for (Combinaison c : this.combinaisons.keySet()) {
            if (c.getIntitule().equals(combinaison)) {
                return c;
            }
        }
        throw new CombinaisonNotFoundException();

    }

    /**
     * Renvoie le score du joueur en paramètre
     * @param joueur le joueur dont on veux récupérer le score
     * @return le Score associé au joueur
     * @throws JoueurNotFoundException si le joueur ne fait pas partie du jeu
     */
    public Score renvoyerScore(AbstractJoueur joueur) throws JoueurNotFoundException {
        for (AbstractJoueur j : joueurs.keySet()) {
            if (joueur.equals(j)) {
                return joueurs.get(j);
            }
        }
        throw new JoueurNotFoundException();
    }

    /**
     * Permet d'ajouter une liste de joueur au jeu
     * Si le jeu a deja une liste de joueur, une nouvelle partie est initialisée
     * @param listeJoueur la
     */
    public void ajouterJoueur(List<AbstractJoueur> listeJoueur) {
        if (joue) {
            //ON LANCE UNE NOUVELLE PARTIE
            for (De d : des.keySet()) {
                des.put(d, false);
            }
            this.joueurs.clear();
            this.ordreJoueurs = null;
            initialiserJeu();
        }
        this.ordreJoueurs = new AbstractJoueur[listeJoueur.size()];
        Set<Combinaison> partieHaute = new LinkedHashSet<Combinaison>();
        Set<Combinaison> partieCentrale = new LinkedHashSet<Combinaison>();
        Set<Combinaison> partieBasse = new LinkedHashSet<Combinaison>();
        for (Combinaison c : this.combinaisons.keySet()) {
            if (combinaisons.get(c).equals(Score.PARTIE_HAUTE)) {
                partieHaute.add(c);
            }
            if (combinaisons.get(c).equals(Score.PARTIE_CENTRALE)) {
                partieCentrale.add(c);
            }
            if (combinaisons.get(c).equals(Score.PARTIE_BASSE)) {
                partieBasse.add(c);
            }
        }
        for (int i = 0; i < listeJoueur.size(); i++) {
            this.joueurs.put(listeJoueur.get(i), new Score(partieHaute, partieCentrale, partieBasse));
            this.ordreJoueurs[i] = listeJoueur.get(i);
        }
        joue = true;
        this.initialiserJeu();
    }

    /**
     * Renvoie le score du joueur courant
     * @return le score du joueur courant
     */
    public Score renvoyerScore() {
        return this.joueurs.get(this.ordreJoueurs[this.indJoueurCourant]);
    }

    /**
     * Permet d'ajouter une ligne au palmarès
     * @param lignePalmares la ligne a ajouter au palmares
     */
    public void ajouterLignePalmares(LignePalmares lignePalmares) {
        this.palmares.ajouterLignePalmares(lignePalmares);
    }

    /**
     * Renvoie le palmares que l'on peut parcourir avec un Iterator<LigneParametre>
     * @return
     */
    public Palmares<LignePalmares> renvoyerPalmares() {
        return this.palmares;
    }

    //A SUPPRIMER SI PAS UTILE POUR INTERFACE GRAPHIQUE
    public Map<AbstractJoueur, Score> getJoueurs() {
        return joueurs;
    }

    /**
     * Retour l'ensemble des combinaisons du jeu
     * @return l'ensemble des combinaisons du jeu dans l'ordre d'insertion
     */
    public Set<Combinaison> getCombinaisons() {
        return combinaisons.keySet();
    }

    /**
     * Indique si la partie est terminée
     * @return vrai si la partie est terminé, faux sinon
     * @throws JoueurNotFoundException si il y a une erreur dans les joueurs
     */
    public boolean partieTermine() throws JoueurNotFoundException{
        for(AbstractJoueur j : joueurs.keySet()){
            if(!renvoyerScore(j).scoreComplet()){
                return false;
            }
        }
        return true;
    }

    /**
     * Charge le palmarès déjà enregistré
     * @throws AccessDataException si l'application n'arrive pas à accéder aux données
     */
    public void chargerPalmares() throws AccessDataException{
        palmares.chargerPalmares();
    }

    public void sauvegarderPalmares() throws AccessDataException{
        palmares.sauvegarderPalmares();
    }
}
